package ranab.io;

import java.io.File;
import java.io.Writer;
import java.io.IOException;
import java.io.Serializable;
import java.text.DecimalFormat;

import java.util.*;

import ranab.io.FileRegularFilter;
import ranab.util.DateUtils;

import com.webdeninteractive.bie.persistent.privileges.*;
import com.webdeninteractive.bie.persistent.queue.*;

import com.webdeninteractive.bie.commons.logging.SM;

/**
 * This class is responsible to handle all virtual directory activities.
 *
 * @author <a href="mailto:rana_b@yahoo.com">Rana Bhattacharyya</a>
 */
public class BIEVirtualDirectory extends VirtualDirectory {
    
    private final static String NEWLINE  = "\r\n";
    private final static String DELIM    = " ";
    
    private String mstRoot        = "/";
    private String mstCurrDir     = "/";
    
    private boolean mbWritePerm   = false;    
    private ranab.server.ftp.usermanager.User user;
 
    /**
     * Default constructor does nothing
     */
    public BIEVirtualDirectory(ranab.server.ftp.usermanager.User user) {
        this.user = user;
    }
                
    /**
     * Set write permission.
     */
    public void setWritePermission(boolean perm) {
        mbWritePerm = perm;
    }
    
    /**
     * Set root directory. Root directory string will always
     * end with '/'.
     */
    public void setRootDirectory(File root) {
        if(root == null) {
            root = new File("/");
        }
        mstRoot = normalizeSeparateChar(root.getAbsolutePath());
        
        // if not ends with '/' - add one
        if(mstRoot.charAt(mstRoot.length()-1) != '/') {
            mstRoot = mstRoot + '/';
        }
        mstCurrDir = "/";
    }
    
    /**
     * Set root directory.
     */
    public void setRootDirectory(String root) throws IOException {
        //File rootFile = new File(root).getCanonicalFile();
        //setRootDirectory(rootFile);
        
        mstRoot = normalizeSeparateChar(root);
        
        // if not ends with '/' - add one
        if(mstRoot.charAt(mstRoot.length()-1) != '/') {
            mstRoot = mstRoot + '/';
        }
        mstCurrDir = "/";
    }
    
    /**
     * Get write permission in this system
     */
    public boolean getWritePermission() {
        return mbWritePerm;
    }
    
    /**
     * Get current working directory.
     */
    public String getCurrentDirectory() {
        return mstCurrDir;
    }
    
    
    /**
     * Get root directory.
     */
    public String getRootDirectory() {
        return mstRoot;
    }
    
    
    /**
     * Get physical name (wrt the machine root).
     */
    public String getPhysicalName(String virtualName) {
        virtualName = normalizeSeparateChar(virtualName);
        return replaceDots(virtualName);
    }
    
    
    /**
     * Get virtual name (wrt the virtual root).
     * The return value will never end with '/' unless it is '/'.
     */
    public String getAbsoluteName(String virtualName) {
        virtualName = normalizeSeparateChar(virtualName);
        String physicalName = replaceDots(virtualName);
        
        String absoluteName = physicalName.substring(mstRoot.length()-1).trim();
        
        return removeLastSlash(absoluteName);
    }
    
    
    /**
     * Get virtual name (wrt the virtual root). The virtual
     * name will never end with '/' unless it is '/'.
     */
    public String getVirtualName(String physicalName) {
        physicalName = normalizeSeparateChar(physicalName);
        if (!physicalName.startsWith(mstRoot)) {
            return null;
        }
        
        String virtualName = physicalName.substring(mstRoot.length()-1).trim();
        return removeLastSlash(virtualName);
    }
    
    
    /**
     * Change directory. The current directory will never have '/'
     * at the end unless it is '/'.
     * @param dirName change the current working directory.
     * @return true if success
     */
    public boolean changeDirectory(String virtualDir) {
                
        String physcialDir = getPhysicalName(virtualDir);
        if (physcialDir.equals("")) {
            physcialDir = "/";
        }
        
        // check directory        
        String lsDirName = normalizeSeparateChar(physcialDir);
        lsDirName = removeLastSlash(lsDirName);
        
        String dirName = "";
        
        //get the directory name with out path
        int lastIndex = lsDirName.lastIndexOf("/");
        if(lastIndex == -1) {
            dirName = lsDirName;
        }
        else {
            dirName = lsDirName.substring(lastIndex + 1);
        }

        try {
            //this is the ftp root name go ahead and change the directory
            if(dirName.equals("FTP_ROOT")) {
                mstCurrDir = physcialDir.substring(mstRoot.length() - 1).trim();
                mstCurrDir = removeLastSlash(mstCurrDir);
                return true;                
            }else {
                //check privileges on this directory                
                Inbox inbox = InboxBuilder.findByName(dirName);
                //if it this users inbox or the user is an administrator, list the contents
                if (Privileges.checkPrivileges(user.getName(),inbox)) {                   
                    mstCurrDir = physcialDir.substring(mstRoot.length() - 1).trim();
                    mstCurrDir = removeLastSlash(mstCurrDir);
                    return true;
                }else {
                    throw new Exception("Access denied for user " + user + " to inbox " + inbox.getName());
                }
            }
            
        } catch (Exception e) {
            SM.warning(e);
        }
        return false;
    }
    
    /**
     * Check read permission.
     */
    public boolean hasReadPermission(String fileName, boolean bPhysical) {
        if(bPhysical) {
            fileName = normalizeSeparateChar(fileName);
        }
        else {
            fileName = getPhysicalName(fileName);
        }
        
        if(!fileName.startsWith(mstRoot)) {
            return false;
        }
        
        return new File(fileName).canRead();
    }
    
    
    /**
     * Chech file write/delete permission.
     */
    public boolean hasWritePermission(String fileName, boolean bPhysical) {
        // no write permission
        if(!mbWritePerm) {
            return false;
        }
        
        // if virtual name - get the physical name
        if(bPhysical) {
            fileName = normalizeSeparateChar(fileName);
        }
        else {
            fileName = getPhysicalName(fileName);
        }
        
        if(!fileName.startsWith(mstRoot)) {
            return false;
        }
        
        return new File(fileName).canWrite();
    }
    
    
    /**
     * Check file create permission.
     */
    public boolean hasCreatePermission(String fileName, boolean bPhysical) {
        // no write permission
        if(!mbWritePerm) {
            return false;
        }
        
        // if virtual name - get the physical name
        if(bPhysical) {
            fileName = normalizeSeparateChar(fileName);
        }
        else {
            fileName = getPhysicalName(fileName);
        }
        
        return fileName.startsWith(mstRoot);
    }
    
    
    /**
     * Print file list. Detail listing.
     * <pre>
     *   -a : display all (including hidden files)
     * </pre>
     * @return true if success
     */
    public boolean printList(String argument, Writer out) throws IOException {
                
        String lsDirName = "./";
        String options = "";
        String pattern   = "*";
        
        // get options, directory name and pattern
        if(argument != null) {
            argument = argument.trim();
            StringBuffer optionsSb = new StringBuffer(4);
            StringTokenizer st = new StringTokenizer(argument, " ");
            while(st.hasMoreTokens()) {
                String token = st.nextToken();
                if(token.charAt(0) == '-') {
                    if (token.length() > 1) {
                        optionsSb.append(token.substring(1));
                    }
                }
                else {
                    lsDirName = token;
                }
            }
            options = optionsSb.toString();
        }
        
        // check options
        boolean bAll = options.indexOf('a') != -1;
        boolean bDetail = options.indexOf('l') != -1;
        
        // check pattern
        lsDirName = getPhysicalName(lsDirName);
        int slashIndex = lsDirName.lastIndexOf('/');
        if( (slashIndex != -1) && (slashIndex != (lsDirName.length() -1)) ) {
            pattern = lsDirName.substring(slashIndex+1);
            lsDirName = lsDirName.substring(0, slashIndex+1);
        }
        
        System.out.println("pattern = " + pattern);
        // check directory        
        lsDirName = normalizeSeparateChar(lsDirName);
        lsDirName = removeLastSlash(lsDirName);
        
        String dirName = "";
        
        //get the direcoty name without the path
        int lastIndex = lsDirName.lastIndexOf("/");
        if(lastIndex == -1) {
            dirName = lsDirName;
        }
        else {
            dirName = lsDirName.substring(lastIndex + 1);
        }
        
        
        try {            
            
            //if its the root directory, list all the resources
            if (dirName.equals("FTP_ROOT")) {
                Iterator rIt = ResourceFactory.findAll(ResourceDescriptor.CHAIN).iterator();
                while (rIt.hasNext()) {
                    Resource resource = (Resource) rIt.next();
                    printLine(resource, out);
                    out.write(NEWLINE);
                    
                }
                
                //print all the queues
                com.webdeninteractive.bie.persistent.privileges.User bieUser = com.webdeninteractive.bie.persistent.privileges.User.findByName(user.getName());                                                
                Iterator inIt = InboxBuilder.findAll().iterator();
                while (inIt.hasNext()) {
                    Inbox inbox = (Inbox) inIt.next();
                    printLine(inbox, out);
                    out.write(NEWLINE);                    
                }                                
            }else {
                // they are changing into a queue directory print all items.
                Inbox in = InboxBuilder.findByName(dirName);
                if (Privileges.checkPrivileges(user.getName(),in)) {
                    Iterator irIt = in.peekAll().iterator();                                
                    while (irIt.hasNext()) {
                        InboxRecord inboxRecord = (InboxRecord) irIt.next();
                        printLine(inboxRecord, out);
                        out.write(NEWLINE);                    
                    }                                                
                }
            }
        }catch (Exception e) {
            SM.warning(e);
            return false;
        }        
        return true;
    }
    
    
    /**
     * Print file list.
     * <pre>
     *   -l : detail listing
     *   -a : display all (including hidden files)
     * </pre>
     * @return true if success
     */
    public boolean printNList(String argument, Writer out) throws IOException {
                
        String lsDirName = "./";
        String options = "";
        String pattern   = "*";
        
        // get options, directory name and pattern
        if(argument != null) {
            argument = argument.trim();
            StringBuffer optionsSb = new StringBuffer(4);
            StringTokenizer st = new StringTokenizer(argument, " ");
            while(st.hasMoreTokens()) {
                String token = st.nextToken();
                if(token.charAt(0) == '-') {
                    if (token.length() > 1) {
                        optionsSb.append(token.substring(1));
                    }
                }
                else {
                    lsDirName = token;
                }
            }
            options = optionsSb.toString();
        }
        
        // check options
        boolean bAll = options.indexOf('a') != -1;
        boolean bDetail = options.indexOf('l') != -1;
        
        // check pattern
        lsDirName = getPhysicalName(lsDirName);
        int slashIndex = lsDirName.lastIndexOf('/');
        if( (slashIndex != -1) && (slashIndex != (lsDirName.length() -1)) ) {
            pattern = lsDirName.substring(slashIndex+1);
            lsDirName = lsDirName.substring(0, slashIndex+1);
        }
                
        lsDirName = normalizeSeparateChar(lsDirName);
        lsDirName = removeLastSlash(lsDirName);
        
        String dirName = "";
        
        //get the direcoty name without the path
        int lastIndex = lsDirName.lastIndexOf("/");
        if(lastIndex == -1) {
            dirName = lsDirName;
        }
        else {
            dirName = lsDirName.substring(lastIndex + 1);
        }
               
                
        // check directory
        /*
        File lstDirObj = new File(lsDirName);
        if(!lstDirObj.exists()) {
            return false;
        }
        if(!lstDirObj.isDirectory()) {
            return false;
        }
        
        // get file list
        File flLst[];
        if ( (pattern == null) || pattern.equals("*") || pattern.equals("") ) {
            flLst = lstDirObj.listFiles();
        }
        else {
            flLst = lstDirObj.listFiles(new FileRegularFilter(pattern));
        }
        */
         
        
        // print file list
        /*
        if (flLst != null) {
            for(int i=0; i<flLst.length; i++) {
                if ( (!bAll) && flLst[i].isHidden() ) {
                    continue;
                }
                if(bDetail) {
                    printLine(flLst[i], out);
                }
                else {
                    out.write(getName(flLst[i]));
                }
                out.write(NEWLINE);
            }
        }
        */
        
        
        try {                        
            //if its the root directory, list all the resources
            if (dirName.equals("FTP_ROOT")) {
                Iterator rIt = ResourceFactory.findAll(ResourceDescriptor.CHAIN).iterator();
                while (rIt.hasNext()) {
                    Resource resource = (Resource) rIt.next();
                    
                    if (bDetail) {
                        printLine(resource, out);
                    }else {
                        out.write(resource.getName());
                    }                    
                    out.write(NEWLINE);
                    
                }
                
                //print all the queues
                com.webdeninteractive.bie.persistent.privileges.User bieUser = com.webdeninteractive.bie.persistent.privileges.User.findByName(user.getName());                                                
                Iterator inIt = InboxBuilder.findAll().iterator();
                while (inIt.hasNext()) {
                    Inbox inbox = (Inbox) inIt.next();
                    
                    if (bDetail) {
                        printLine(inbox, out);
                    }else {
                        out.write(inbox.getName());
                    }                    
                    out.write(NEWLINE);                    
                }                                
            }else {
                // they are changing into a queue directory print all items.
                Inbox in = InboxBuilder.findByName(dirName);
                if (Privileges.checkPrivileges(user.getName(),in)) {
                    Iterator irIt = in.peekAll().iterator();                                
                    while (irIt.hasNext()) {
                        InboxRecord inboxRecord = (InboxRecord) irIt.next();
                        
                        if (bDetail) {
                            printLine(inboxRecord, out);
                        }else {
                            out.write(inboxRecord.getName());
                        }
                        
                        out.write(NEWLINE);                    
                    }                                                
                }
            }
        }catch (Exception e) {
            SM.warning(e);
            return false;
        }        
                
        return true;
    }
    
    /**
     * Get file owner.
     */
    private String getOwner(File fl) {
        return "user";
    }
    
    
    /**
     * Get group name
     */
    private String getGroup(File fl) {
        return "group";
    }
    
    /**
     * Get file owner.
     */
    private String getOwner(Resource resource) {
        return "user";
    }
    
    
    /**
     * Get group name
     */
    private String getGroup(Resource resource) {
        return "group";
    }
    
    /**
     * Get file owner.
     */
    private String getOwner(Inbox inbox) {
        return "user";
    }
    
    
    /**
     * Get group name
     */
    private String getGroup(Inbox inbox) {
        return "group";
    }
    
    /**
     * Get file owner.
     */
    private String getOwner(InboxRecord inboxRecord) {
        return "user";
    }
    
    
    /**
     * Get group name
     */
    private String getGroup(InboxRecord inboxRecord) {
        return "group";
    }
    
    /**
     * Get link count
     */
    private String getLinkCount(File fl) {
        if(fl.isDirectory()) {
            return String.valueOf(3);
        }
        else {
            return String.valueOf(1);
        }
    }
    
    private String getLinkCount(Resource resource) {
        return String.valueOf(1);
    }
    
    private String getLinkCount(Inbox inbox) {
        return String.valueOf(3);
    }

    private String getLinkCount(InboxRecord inboxRecord) {
        return String.valueOf(1);
    }
    
    
    /**
     * Get size
     */
    private String getLength(File fl) {
        String initStr = "            ";
        long sz = 0;
        if(fl.isFile()) {
            sz = fl.length();
        }
        String szStr = String.valueOf(sz);
        if(szStr.length() > initStr.length()) {
            return szStr;
        }
        return initStr.substring(0, initStr.length() - szStr.length()) + szStr;
    }
    
    private String getLength(Resource resource) {
        String initStr = "            ";
        long sz = 100;
        String szStr = String.valueOf(sz);
        if(szStr.length() > initStr.length()) {
            return szStr;
        }
        return initStr.substring(0, initStr.length() - szStr.length()) + szStr;
    }

    private String getLength(Inbox inbox) {
        String initStr = "            ";
        long sz = 0;
        String szStr = String.valueOf(sz);
        if(szStr.length() > initStr.length()) {
            return szStr;
        }
        return initStr.substring(0, initStr.length() - szStr.length()) + szStr;
    }
    
    private String getLength(InboxRecord inboxRecord) {
        String initStr = "            ";
        long sz = inboxRecord.getContent().length;
        String szStr = String.valueOf(sz);
        if(szStr.length() > initStr.length()) {
            return szStr;
        }
        return initStr.substring(0, initStr.length() - szStr.length()) + szStr;
    }
    
    
    /**
     * Get last modified date string.
     */
    private String getLastModified(File fl) {
        long modTime = fl.lastModified();
        Date date = new Date(modTime);
        return DateUtils.getUnixDate(date);
    }
    
    private String getLastModified(Resource resource) {
        Date date = new Date();
        return DateUtils.getUnixDate(date);
    }
    
    private String getLastModified(Inbox inbox) {
        Date date = new Date();
        return DateUtils.getUnixDate(date);
    }
    
    private String getLastModified(InboxRecord inboxRecord) {
        return DateUtils.getUnixDate(inboxRecord.getCreated());
    }
    /**
     * Get file name.
     */
    private String getName(File fl) {
        
        //removed
        String flName = fl.getName();
        flName = normalizeSeparateChar(flName);
        
        int lastIndex = flName.lastIndexOf("/");
        if(lastIndex == -1) {
            return flName;
        }
        else {
            return flName.substring(lastIndex + 1);
        }
    }
    
    private String getName(Resource resource) {
        return resource.getName();
    }
    
    private String getName(Inbox inbox) {
        return inbox.getName();
    }
    
    private String getName(InboxRecord inboxRecord) {
        return inboxRecord.getName();
    }
    
    /**
     * Get permission string.
     */
    private String getPermission(File fl) {
        StringBuffer sb = new StringBuffer(13);
        if(fl.isDirectory()) {
            sb.append('d');
        }
        else {
            sb.append('-');
        }
        
        if (fl.canRead()) {
            sb.append('r');
        }
        else {
            sb.append('-');
        }
        
        if (mbWritePerm && fl.canWrite()) {
            sb.append('w');
        }
        else {
            sb.append('-');
        }
        sb.append("-------");
        
        return sb.toString();
    }
    
    private String getPermission(Resource resource) {
        StringBuffer sb = new StringBuffer(13);
        sb.append('-');
        sb.append('r');
        sb.append('-');
        sb.append("-------");
        return sb.toString();
    }

    private String getPermission(Inbox inbox) {
        StringBuffer sb = new StringBuffer(13);
        sb.append('d');
        sb.append('r');
        sb.append('-');
        sb.append("-------");
        return sb.toString();
    }
    
    private String getPermission(InboxRecord inboxRecord) {
        StringBuffer sb = new StringBuffer(13);
        sb.append('-');
        sb.append('r');
        sb.append('-');
        sb.append("-------");
        return sb.toString();
    }
    
    /**
     * Normalize separate characher. Separate character should be '/' always.
     */
    private String normalizeSeparateChar(String pathName) {
        pathName = pathName.replace(File.separatorChar, '/');
        pathName = pathName.replace('\\', '/');
        return pathName;
    }
    
    
    /**
     * Replace dots. Returns physical name.
     * @param inArg the virtaul name
     */
    private String replaceDots(String inArg) {
        
        // get the starting directory
        String resArg;
        if(inArg.charAt(0) != '/') {
            resArg = mstRoot + mstCurrDir.substring(1);
        }
        else {
            resArg = mstRoot;
        }
        
        // strip last '/'
        if(resArg.charAt(resArg.length() -1) == '/') {
            resArg = resArg.substring(0, resArg.length()-1);
        }
        
        // replace ., ~ and ..
        StringTokenizer st = new StringTokenizer(inArg, "/");
        while(st.hasMoreTokens()) {
            
            String tok = st.nextToken().trim();
            
            // . => current directory
            if(tok.equals(".")) {
                continue;
            }
            
            // .. => parent directory (if not root)
            if(tok.equals("..")) {
                if(resArg.startsWith(mstRoot)) {
                    int slashIndex = resArg.lastIndexOf('/');
                    if(slashIndex != -1) {
                        resArg = resArg.substring(0, slashIndex);
                    }
                }
                continue;
            }
            
            // ~ => home directory (in this case /)
            if (tok.equals("~")) {
                resArg = mstRoot.substring(0, mstRoot.length()-1).trim();
                continue;
            }
            
            resArg = resArg + '/' + tok;
        }
        
        // add last slash if necessary
        if( !inArg.equals("") && (inArg.charAt(inArg.length()-1)=='/') ) {
            resArg = resArg + '/';
        }
        
        // final check
        if (resArg.length() < mstRoot.length()) {
            resArg = mstRoot;
        }
        
        return resArg;
    }
    
    
    /**
     * Get each directory line.
     */
    private void printLine(File fl, Writer out) throws IOException {
        out.write(getPermission(fl));
        out.write(DELIM);
        out.write(DELIM);
        out.write(DELIM);
        out.write(getLinkCount(fl));
        out.write(DELIM);
        out.write(getOwner(fl));
        out.write(DELIM);
        out.write(getGroup(fl));
        out.write(DELIM);
        out.write(getLength(fl));
        out.write(DELIM);
        out.write(getLastModified(fl));
        out.write(DELIM);
        out.write(getName(fl));
    }
    
    /**
     * Get each directory line.
     */
    private void printLine(Resource resource, Writer out) throws IOException {
        out.write(getPermission(resource));
        out.write(DELIM);
        out.write(DELIM);
        out.write(DELIM);
        out.write(getLinkCount(resource));
        out.write(DELIM);
        out.write(getOwner(resource));
        out.write(DELIM);
        out.write(getGroup(resource));
        out.write(DELIM);
        out.write(getLength(resource));
        out.write(DELIM);
        out.write(getLastModified(resource));
        out.write(DELIM);
        out.write(getName(resource));
    }

    private void printLine(Inbox inbox, Writer out) throws IOException {
        out.write(getPermission(inbox));
        out.write(DELIM);
        out.write(DELIM);
        out.write(DELIM);
        out.write(getLinkCount(inbox));
        out.write(DELIM);
        out.write(getOwner(inbox));
        out.write(DELIM);
        out.write(getGroup(inbox));
        out.write(DELIM);
        out.write(getLength(inbox));
        out.write(DELIM);
        out.write(getLastModified(inbox));
        out.write(DELIM);
        out.write(getName(inbox));
    }
    
    private void printLine(InboxRecord inboxRecord, Writer out) throws IOException {
        out.write(getPermission(inboxRecord));
        out.write(DELIM);
        out.write(DELIM);
        out.write(DELIM);
        out.write(getLinkCount(inboxRecord));
        out.write(DELIM);
        out.write(getOwner(inboxRecord));
        out.write(DELIM);
        out.write(getGroup(inboxRecord));
        out.write(DELIM);
        out.write(getLength(inboxRecord));
        out.write(DELIM);
        out.write(getLastModified(inboxRecord));
        out.write(DELIM);
        out.write(getName(inboxRecord));
    }

    
    
    /**
     * If the string is not '/', remove last slash.
     */
    private String removeLastSlash(String str) {
        if ( (str.length()>1) && (str.charAt(str.length()-1)=='/') ) {
            str = str.substring(0, str.length()-1);
        }
        return str;
    }
    
}
